/**
 * Yarbu is a proof of concept of a JavaScript HTTP server.
 * Copyright (C) 2011 Antonio Hernandez Diaz <ahdiaz@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $Id: JsHandler.java 2 2011-01-08 06:03:19Z ahdiaz $
 */

package net.ahdiaz.yarbu;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.ahdiaz.yarbu.sql.ConnectionFactory;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

public class JsHandler extends AbstractHandler {

	private final int FLAG = ScriptableObject.READONLY | ScriptableObject.PERMANENT;
	
	@Override
	public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
		throws IOException, ServletException {
		
		if (!target.endsWith(".js")) {
			
			File path = new File(request.getPathTranslated());
			
			if (!path.isDirectory()) {
				baseRequest.setHandled(false);
				return;
			}
			
			if (!target.endsWith("/")) {
				response.sendRedirect(target + "/");
				baseRequest.setHandled(true);
				return;
			}
				
			FilenameFilter filter = new FilenameFilter() {
				
				@Override
				public boolean accept(File dir, String name) {
					return name.equals("index.js") || name.equals("main.js");
				}
			};
			
			String[] indexList = path.list(filter);
			Boolean redirect = false;
			String uri = null;
			
			if (indexList != null) {
				
				for (String index : indexList) {
					uri = index;
					redirect = true;
					break;
				}
			}
			
			if (redirect) {
				response.sendRedirect(uri);
			}
			baseRequest.setHandled(redirect);
			return;
		}
		
        baseRequest.setHandled(true);
        Shell shell = new Shell();
        
        defineProperties(shell, baseRequest, response);
        shell.processSource(baseRequest.getPathTranslated());
        outputResponse(shell, response);
        
        shell.exit();
	}
	
	private void outputResponse(Shell shell, HttpServletResponse response) throws IOException {
		
		Scriptable resp = (Scriptable) ScriptableObject.getProperty(shell, "Response");
		
		Double status = Context.toNumber(ScriptableObject.getProperty(resp, "status"));
		response.setStatus(status.intValue());

		Scriptable headers = (Scriptable) ScriptableObject.getProperty(resp, "headers");
		Object[] keys = headers.getIds();
		for (int i=0,l=keys.length; i<l; i++) {
			String key = (String) keys[i];
			String value = (String) ScriptableObject.getProperty(headers, key);
			response.setHeader(key, value);
		}
		
		String[] buffer = (String[]) Context.jsToJava(Context.toObject((Scriptable) ScriptableObject.getProperty(resp, "body"), shell), String[].class);
		StringBuffer body = new StringBuffer();
		for (int i=0,l=buffer.length; i<l; i++) {
			body.append(buffer[i] + "\n");
		}
		
		response.setContentLength(body.length());
		response.getWriter().println(body);
	}
	
	private void defineProperties(Shell shell, Request request, HttpServletResponse response) {

		try {
			ScriptableObject.defineClass(shell, ConnectionFactory.class);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		
		Scriptable req = createRequest(shell, request);
		shell.defineProperty("Request", req, FLAG);

		Scriptable resp = createResponse(shell, response);
		shell.defineProperty("Response", resp, FLAG);

		Scriptable params = createParams(shell, request);
		shell.defineProperty("Params", params, FLAG);
	}
	
	private Scriptable createRequest(Shell shell, Request request) {		
		Scriptable req = shell.getContext().newObject(shell);
		ScriptableObject.defineProperty(req, "cookies", Context.toString(request.getCookies()), FLAG);
		ScriptableObject.defineProperty(req, "remoteAddr", Context.toString(request.getRemoteAddr()), FLAG);
		ScriptableObject.defineProperty(req, "remoteHost", Context.toString(request.getRemoteHost()), FLAG);
		ScriptableObject.defineProperty(req, "remotePort", Context.toString(request.getRemotePort()), FLAG);
		ScriptableObject.defineProperty(req, "remoteUser", Context.toString(request.getRemoteUser()), FLAG);
		ScriptableObject.defineProperty(req, "localName", Context.toString(request.getLocalName()), FLAG);
		ScriptableObject.defineProperty(req, "localAddr", Context.toString(request.getLocalAddr()), FLAG);
		ScriptableObject.defineProperty(req, "localPort", Context.toString(request.getLocalPort()), FLAG);
		ScriptableObject.defineProperty(req, "uri", Context.toString(request.getUri()), FLAG);
		ScriptableObject.defineProperty(req, "requestURI", Context.toString(request.getRequestURI()), FLAG);
		ScriptableObject.defineProperty(req, "requestURL", Context.toString(request.getRequestURL()), FLAG);
		ScriptableObject.defineProperty(req, "rootURL", Context.toString(request.getRootURL()), FLAG);
		ScriptableObject.defineProperty(req, "serverName", Context.toString(request.getServerName()), FLAG);
		ScriptableObject.defineProperty(req, "serverPort", Context.toString(request.getServerPort()), FLAG);
		ScriptableObject.defineProperty(req, "scheme", Context.toString(request.getScheme()), FLAG);
		ScriptableObject.defineProperty(req, "method", Context.toString(request.getMethod()), FLAG);
		ScriptableObject.defineProperty(req, "protocol", Context.toString(request.getProtocol()), FLAG);
		ScriptableObject.defineProperty(req, "queryString", Context.toString(request.getQueryString()), FLAG);
		ScriptableObject.defineProperty(req, "locale", Context.toString(request.getLocale()), FLAG);
		ScriptableObject.defineProperty(req, "scriptFile", Context.toString(request.getPathTranslated()), FLAG);
		ScriptableObject.defineProperty(req, "scriptDir", Context.toString(new File(request.getPathTranslated()).getParent()), FLAG);
		ScriptableObject.defineProperty(req, "documentRoot", Context.toString(request.getContext().getRealPath("/")), FLAG);
		ScriptableObject.defineProperty(req, "serverRoot", Context.toString(System.getProperty("user.dir")), FLAG);
		ScriptableObject.defineProperty(req, "timestamp", Context.toString(request.getTimeStamp()), FLAG);
		return req;
	}
	
	private Scriptable createResponse(Shell shell, HttpServletResponse response) {
		Scriptable resp = shell.getContext().newObject(shell);
		ScriptableObject.defineProperty(resp, "status", "200", ScriptableObject.PERMANENT);
		ScriptableObject.defineProperty(resp, "headers", shell.getContext().newObject(shell), ScriptableObject.PERMANENT);
		ScriptableObject.defineProperty(resp, "body", shell.getContext().newArray(shell, 0), ScriptableObject.PERMANENT);
		return resp;
	}
	
	private Scriptable createParams(Shell shell, Request request) {

		Scriptable params = shell.getContext().newObject(shell);
		Enumeration<?> names = request.getParameterNames();
		
		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			// NOTE use getParameterValues() if could be more than one of the parameter with the same name.
			ScriptableObject.defineProperty(params, name, Context.toString(request.getParameter(name)), FLAG);
		}
		return params;
	}
	
	public void p(String s) {
		System.out.println(s);
	}

}
